package com.google.code.jmarias.server.phases;

import com.google.code.jmarias.common.action.GameAction;
import com.google.code.jmarias.common.NewPlayer;
import com.google.code.jmarias.common.NewTable;
import com.google.code.jmarias.common.Token;
import com.google.code.jmarias.common.action.SignedAction;
import com.google.code.jmarias.server.tcp.ContactablePlayer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author Ondrej Michal
 */
public abstract class NewAbstractPhase {

    private static Log log = LogFactory.getLog(NewAbstractPhase.class);

    private Set<ActionListener> listeners = new HashSet<ActionListener>();

    private NewTable table;

    private NewPlayer playingNow;

    public NewAbstractPhase(NewPlayer playingNow) {
        this.playingNow = playingNow;
    }

    protected void addListener(ActionListener listener) {
        listeners.add(listener);
    }

    /** Musi vyhovovat jen jeden listener, jinak konci vyjimkou. */
    public NewAbstractPhase processAction(SignedAction action) {
        NewAbstractPhase result = null;
        for (ActionListener listener : listeners) {
            if (listener.accepts(action)) {
                if (result != null) {
                    throw new RuntimeException("Chyba serveru - akci " + action + " zpracovava vice listeneru.");
                }
                result = listener.actionPerformed(action, table);
            }
        }
        log.debug(table);
        if (result == null) {
            System.out.println("Akce nebyla zachycena zadnym listenerem, zrejme tedy slo o nepovolenou akci, a proto byla ignorovana.");
            return null;
        } else {
            log.debug("Prechod do faze " + result);
            NewPlayer p = result.getPlayingNow();
            Token token = new Token(p.getClient());
            for (NewPlayer player : table.getPlayers()) {
                if (! player.equals(p)) {
                    // server si tam uklada ContactablePlayer, takze by to tam melo byt
                    ((ContactablePlayer)player).sendAction(token);
                }
            }
            token.setAllowedActions(result.getSupportedActions());
            log.debug("Broadcasting token \"" + token + '\"');
            // server si tam uklada ContactablePlayer, takze by to tam melo byt
            ((ContactablePlayer)p).sendAction(token);
            return result;
        }
    }

    public List<Class> getSupportedActions() {
        List<Class> result = new ArrayList<Class>();
        for (ActionListener listener : listeners) {
            result.add(listener.acceptedAction);
        }
        return result;
    }

    public void broadcast(SignedAction action, List<NewPlayer> recipients) {
        log.debug("Broadcasting action " + action);
        action.updateTable(table);
        for (NewPlayer recipient : recipients) {
            if (table.getPlayers().contains(recipient)) {
                // server si tam uklada ContactablePlayer, takze by to tam melo byt
                ((ContactablePlayer)recipient).sendAction(action);
            }
        }

        if (action.getRawAction() instanceof GameAction) {
            GameAction hiddenAction = ((GameAction) action.getRawAction()).hideCards();
            for (NewPlayer player : table.getPlayers()) {
                if (! recipients.contains(player)) {
                    // server si tam uklada ContactablePlayer, takze by to tam melo byt
                    ((ContactablePlayer)player).sendAction(new SignedAction(action.getSender(), hiddenAction));
                }
            }
        } else {
            throw new RuntimeException("RawAction not instance of GameAction.");
        }
    }

    /** Posle akci vsem hracum, posilane karty skryje vsem krome recipienta (pokud neni null). */
    public void broadcast(SignedAction action, NewPlayer recipient) {
        broadcast(action, Arrays.asList(recipient));
    }

    /**
     * Posle akci vsem hracum, posilane karty vsem skryje.
     * Z duvodu bezpecnosti by se spise mely karty skryvat, ale pak by to mohlo byt neefektivni,
     * protoze by se kazda akce prohledavala pres reflexi
     *
     * bezpecnost je dulezitejsi nez vykon a navic kontrakt se pozdeji nesmi menit, jinak by to zpusobilo zmatky
     */
    public void broadcast(SignedAction action) {
        List<NewPlayer> list = Collections.emptyList();
        broadcast(action, list);
    }

    public NewTable getTable() {
        return table;
    }

    public void setTable(NewTable table) {
        this.table = table;
    }

    public NewPlayer getPlayingNow() {
        return playingNow;
    }

    

    protected abstract static class ActionListener {

        public NewAbstractPhase actionPerformed(SignedAction action, NewTable table) {
            return processAction(action, table);
        }

        protected abstract NewAbstractPhase processAction(SignedAction action, NewTable table);

        private Class acceptedAction;

        public ActionListener(Class acceptedAction) {
            this.acceptedAction = acceptedAction;
        }

        /**
         * Vraci true, pokud trida zadane akce je shodna s acceptedAction nebo
         * pokud zadana akce je null a acceptedAction je take null.
         */
        public boolean accepts(SignedAction action) {
            if (action == null) {
                return acceptedAction == null;
            }
            return acceptedAction.isAssignableFrom(action.getRawAction().getClass());
        }

    }
}
